// Copyright (C) 2006-2010 Luke Hoschke
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

// B3D Mesh loader
// File format designed by Mark Sibly for the Blitz3D engine and has been
// declared public domain

#include "IrrCompileConfig.h"
#ifdef _IRR_COMPILE_WITH_B3D_LOADER_

#include "CB3DMeshFileLoader.h"

#include "IVideoDriver.h"
#include "IFileSystem.h"
#include "os.h"

#ifdef _DEBUG
#define _B3D_READER_DEBUG
#endif

namespace irr
{
    namespace scene
    {

        //! Constructor
        CB3DMeshFileLoader::CB3DMeshFileLoader(scene::ISceneManager* smgr)
            : SceneManager(smgr), AnimatedMesh(0), B3DFile(0), NormalsInFile(false),
            HasVertexColors(false), ShowWarning(true)
        {
#ifdef _DEBUG
            setDebugName("CB3DMeshFileLoader");
#endif
        }


        //! returns true if the file maybe is able to be loaded by this class
        //! based on the file extension (e.g. ".bsp")
        bool CB3DMeshFileLoader::isALoadableFileExtension(const io::path& filename) const
        {
            return core::hasFileExtension ( filename, "b3d" );
        }


        //! creates/loads an animated mesh from the file.
        //! \return Pointer to the created mesh. Returns 0 if loading failed.
        //! If you no longer need the mesh, you should call IAnimatedMesh::drop().
        //! See IReferenceCounted::drop() for more information.
        IAnimatedMesh* CB3DMeshFileLoader::createMesh(io::IReadFile* f)
        {
            if (!f)
                return 0;

            B3DFile = f;
            AnimatedMesh = new scene::CSkinnedMesh();
            ShowWarning = true; // If true a warning is issued if too many textures are used

            if ( load() )
            {
                AnimatedMesh->finalize();
            }
            else
            {
                AnimatedMesh->drop();
                AnimatedMesh = 0;
            }

            return AnimatedMesh;
        }


        bool CB3DMeshFileLoader::load()
        {
            B3dStack.clear();

            NormalsInFile=false;
            HasVertexColors=false;

            //------ Get header ------

            SB3dChunkHeader header;
            B3DFile->read(&header, sizeof(header));
#ifdef __BIG_ENDIAN__
            header.size = os::Byteswap::byteswap(header.size);
#endif

            if ( strncmp( header.name, "BB3D", 4 ) != 0 )
            {
                os::Printer::log("File is not a b3d file. Loading failed (No header found)", B3DFile->getFileName(), ELL_ERROR);
                return false;
            }

            // Add main chunk...
            B3dStack.push_back(SB3dChunk(header, B3DFile->getPos()-8));

            // Get file version, but ignore it, as it's not important with b3d files...
            s32 fileVersion;
            B3DFile->read(&fileVersion, sizeof(fileVersion));
#ifdef __BIG_ENDIAN__
            fileVersion = os::Byteswap::byteswap(fileVersion);
#endif

            //------ Read main chunk ------

            while ( (B3dStack.getLast().startposition + B3dStack.getLast().length) > B3DFile->getPos() )
            {
                B3DFile->read(&header, sizeof(header));
#ifdef __BIG_ENDIAN__
                header.size = os::Byteswap::byteswap(header.size);
#endif
                B3dStack.push_back(SB3dChunk(header, B3DFile->getPos()-8));

                if ( strncmp( B3dStack.getLast().name, "TEXS", 4 ) == 0 )
                {
                    if (!readChunkTEXS())
                        return false;
                }
                else if ( strncmp( B3dStack.getLast().name, "BRUS", 4 ) == 0 )
                {
                    if (!readChunkBRUS())
                        return false;
                }
                else if ( strncmp( B3dStack.getLast().name, "NODE", 4 ) == 0 )
                {
                    if (!readChunkNODE((CSkinnedMesh::SJoint*)0) )
                        return false;
                }
                else
                {
                    os::Printer::log("Unknown chunk found in mesh base - skipping");
                    B3DFile->seek(B3dStack.getLast().startposition + B3dStack.getLast().length);
                    B3dStack.erase(B3dStack.size()-1);
                }
            }

            B3dStack.clear();

            BaseVertices.clear();
            AnimatedVertices_VertexID.clear();
            AnimatedVertices_BufferID.clear();

            Materials.clear();
            Textures.clear();

            return true;
        }


        bool CB3DMeshFileLoader::readChunkNODE(CSkinnedMesh::SJoint *inJoint)
        {
            CSkinnedMesh::SJoint *joint = AnimatedMesh->addJoint(inJoint);
            readString(joint->Name);

#ifdef _B3D_READER_DEBUG
            os::Printer::log("read ChunkNODE", joint->Name.c_str());
#endif

            f32 position[3], scale[3], rotation[4];

            readFloats(position, 3);
            readFloats(scale, 3);
            readFloats(rotation, 4);

            joint->Animatedposition = core::vector3df(position[0],position[1],position[2]) ;
            joint->Animatedscale = core::vector3df(scale[0],scale[1],scale[2]);
            joint->Animatedrotation = core::quaternion(rotation[1], rotation[2], rotation[3], rotation[0]);

            //Build LocalMatrix:

            core::matrix4 positionMatrix;
            positionMatrix.setTranslation( joint->Animatedposition );
            core::matrix4 scaleMatrix;
            scaleMatrix.setScale( joint->Animatedscale );
            core::matrix4 rotationMatrix = joint->Animatedrotation.getMatrix();

            joint->LocalMatrix = positionMatrix * rotationMatrix * scaleMatrix;

            if (inJoint)
                joint->GlobalMatrix = inJoint->GlobalMatrix * joint->LocalMatrix;
            else
                joint->GlobalMatrix = joint->LocalMatrix;

            while(B3dStack.getLast().startposition + B3dStack.getLast().length > B3DFile->getPos()) // this chunk repeats
            {
                SB3dChunkHeader header;
                B3DFile->read(&header, sizeof(header));
#ifdef __BIG_ENDIAN__
                header.size = os::Byteswap::byteswap(header.size);
#endif

                B3dStack.push_back(SB3dChunk(header, B3DFile->getPos()-8));

                if ( strncmp( B3dStack.getLast().name, "NODE", 4 ) == 0 )
                {
                    if (!readChunkNODE(joint))
                        return false;
                }
                else if ( strncmp( B3dStack.getLast().name, "MESH", 4 ) == 0 )
                {
                    if (!readChunkMESH(joint))
                        return false;
                }
                else if ( strncmp( B3dStack.getLast().name, "BONE", 4 ) == 0 )
                {
                    if (!readChunkBONE(joint))
                        return false;
                }
                else if ( strncmp( B3dStack.getLast().name, "KEYS", 4 ) == 0 )
                {
                    if(!readChunkKEYS(joint))
                        return false;
                }
                else if ( strncmp( B3dStack.getLast().name, "ANIM", 4 ) == 0 )
                {
                    if (!readChunkANIM())
                        return false;
                }
                else
                {
                    os::Printer::log("Unknown chunk found in node chunk - skipping");
                    B3DFile->seek(B3dStack.getLast().startposition + B3dStack.getLast().length);
                    B3dStack.erase(B3dStack.size()-1);
                }
            }

            B3dStack.erase(B3dStack.size()-1);

            return true;
        }


        bool CB3DMeshFileLoader::readChunkMESH(CSkinnedMesh::SJoint *inJoint)
        {
#ifdef _B3D_READER_DEBUG
            os::Printer::log("read ChunkMESH");
#endif

            const s32 vertices_Start=BaseVertices.size(); //B3Ds have Vertex ID's local within the mesh I don't want this

            s32 brush_id;
            B3DFile->read(&brush_id, sizeof(brush_id));
#ifdef __BIG_ENDIAN__
            brush_id = os::Byteswap::byteswap(brush_id);
#endif

            NormalsInFile=false;
            HasVertexColors=false;

            while((B3dStack.getLast().startposition + B3dStack.getLast().length) > B3DFile->getPos()) //this chunk repeats
            {
                SB3dChunkHeader header;
                B3DFile->read(&header, sizeof(header));
#ifdef __BIG_ENDIAN__
                header.size = os::Byteswap::byteswap(header.size);
#endif

                B3dStack.push_back(SB3dChunk(header, B3DFile->getPos()-8));

                if ( strncmp( B3dStack.getLast().name, "VRTS", 4 ) == 0 )
                {
                    if (!readChunkVRTS(inJoint))
                        return false;
                }
                else if ( strncmp( B3dStack.getLast().name, "TRIS", 4 ) == 0 )
                {
                    scene::SSkinMeshBuffer *meshBuffer = AnimatedMesh->addMeshBuffer();

                    if (brush_id!=-1)
                    {
                        loadTextures(Materials[brush_id]);
                        meshBuffer->Material=Materials[brush_id].Material;
                    }

                    if(readChunkTRIS(meshBuffer,AnimatedMesh->getMeshBuffers().size()-1, vertices_Start)==false)
                        return false;

                    if (!NormalsInFile)
                    {
                        s32 i;

                        for ( i=0; i<(s32)meshBuffer->Indices.size(); i+=3)
                        {
                            core::plane3df p(meshBuffer->getVertex(meshBuffer->Indices[i+0])->Pos,
                                meshBuffer->getVertex(meshBuffer->Indices[i+1])->Pos,
                                meshBuffer->getVertex(meshBuffer->Indices[i+2])->Pos);

                            meshBuffer->getVertex(meshBuffer->Indices[i+0])->Normal += p.Normal;
                            meshBuffer->getVertex(meshBuffer->Indices[i+1])->Normal += p.Normal;
                            meshBuffer->getVertex(meshBuffer->Indices[i+2])->Normal += p.Normal;
                        }

                        for ( i = 0; i<(s32)meshBuffer->getVertexCount(); ++i )
                        {
                            meshBuffer->getVertex(i)->Normal.normalize();
                            BaseVertices[vertices_Start+i].Normal=meshBuffer->getVertex(i)->Normal;
                        }
                    }
                }
                else
                {
                    os::Printer::log("Unknown chunk found in mesh - skipping");
                    B3DFile->seek(B3dStack.getLast().startposition + B3dStack.getLast().length);
                    B3dStack.erase(B3dStack.size()-1);
                }
            }

            B3dStack.erase(B3dStack.size()-1);

            return true;
        }


        /*
        VRTS:
        int flags                   ;1=normal values present, 2=rgba values present
        int tex_coord_sets          ;texture coords per vertex (eg: 1 for simple U/V) max=8
        but we only support 3
        int tex_coord_set_size      ;components per set (eg: 2 for simple U/V) max=4
        {
        float x,y,z                 ;always present
        float nx,ny,nz              ;vertex normal: present if (flags&1)
        float red,green,blue,alpha  ;vertex color: present if (flags&2)
        float tex_coords[tex_coord_sets][tex_coord_set_size]	;tex coords
        }
        */
        bool CB3DMeshFileLoader::readChunkVRTS(CSkinnedMesh::SJoint *inJoint)
        {
#ifdef _B3D_READER_DEBUG
            os::Printer::log("read ChunkVRTS");
#endif

            const s32 max_tex_coords = 3;
            s32 flags, tex_coord_sets, tex_coord_set_size;

            B3DFile->read(&flags, sizeof(flags));
            B3DFile->read(&tex_coord_sets, sizeof(tex_coord_sets));
            B3DFile->read(&tex_coord_set_size, sizeof(tex_coord_set_size));
#ifdef __BIG_ENDIAN__
            flags = os::Byteswap::byteswap(flags);
            tex_coord_sets = os::Byteswap::byteswap(tex_coord_sets);
            tex_coord_set_size = os::Byteswap::byteswap(tex_coord_set_size);
#endif

            if (tex_coord_sets >= max_tex_coords || tex_coord_set_size >= 4) // Something is wrong
            {
                os::Printer::log("tex_coord_sets or tex_coord_set_size too big", B3DFile->getFileName(), ELL_ERROR);
                return false;
            }

            //------ Allocate Memory, for speed -----------//

            s32 numberOfReads = 3;

            if (flags & 1)
            {
                NormalsInFile = true;
                numberOfReads += 3;
            }
            if (flags & 2)
            {
                numberOfReads += 4;
                HasVertexColors=true;
            }

            numberOfReads += tex_coord_sets*tex_coord_set_size;

            const s32 memoryNeeded = (B3dStack.getLast().length / sizeof(f32)) / numberOfReads;

            BaseVertices.reallocate(memoryNeeded + BaseVertices.size() + 1);
            AnimatedVertices_VertexID.reallocate(memoryNeeded + AnimatedVertices_VertexID.size() + 1);

            //--------------------------------------------//

            while( (B3dStack.getLast().startposition + B3dStack.getLast().length) > B3DFile->getPos()) // this chunk repeats
            {
                f32 position[3];
                f32 normal[3]={0.f, 0.f, 0.f};
                f32 color[4]={1.0f, 1.0f, 1.0f, 1.0f};
                f32 tex_coords[max_tex_coords][4];

                readFloats(position, 3);

                if (flags & 1)
                    readFloats(normal, 3);
                if (flags & 2)
                    readFloats(color, 4);

                for (s32 i=0; i<tex_coord_sets; ++i)
                    readFloats(tex_coords[i], tex_coord_set_size);

                f32 tu=0.0f, tv=0.0f;
                if (tex_coord_sets >= 1 && tex_coord_set_size >= 2)
                {
                    tu=tex_coords[0][0];
                    tv=tex_coords[0][1];
                }

                f32 tu2=0.0f, tv2=0.0f;
                if (tex_coord_sets>1 && tex_coord_set_size>1)
                {
                    tu2=tex_coords[1][0];
                    tv2=tex_coords[1][1];
                }

                // Create Vertex...
                video::S3DVertex2TCoords Vertex(position[0], position[1], position[2],
                    normal[0], normal[1], normal[2],
                    video::SColorf(color[0], color[1], color[2], color[3]).toSColor(),
                    tu, tv, tu2, tv2);

                // Transform the Vertex position by nested node...
                inJoint->GlobalMatrix.transformVect(Vertex.Pos);
                inJoint->GlobalMatrix.rotateVect(Vertex.Normal);

                //Add it...
                BaseVertices.push_back(Vertex);

                AnimatedVertices_VertexID.push_back(-1);
                AnimatedVertices_BufferID.push_back(-1);
            }

            B3dStack.erase(B3dStack.size()-1);

            return true;
        }


        bool CB3DMeshFileLoader::readChunkTRIS(scene::SSkinMeshBuffer *meshBuffer, u32 meshBufferID, s32 vertices_Start)
        {
#ifdef _B3D_READER_DEBUG
            os::Printer::log("read ChunkTRIS");
#endif

            bool showVertexWarning=false;

            s32 triangle_brush_id; // Note: Irrlicht can't have different brushes for each triangle (using a workaround)
            B3DFile->read(&triangle_brush_id, sizeof(triangle_brush_id));
#ifdef __BIG_ENDIAN__
            triangle_brush_id = os::Byteswap::byteswap(triangle_brush_id);
#endif

            SB3dMaterial *B3dMaterial;

            if (triangle_brush_id != -1)
            {
                loadTextures(Materials[triangle_brush_id]);
                B3dMaterial = &Materials[triangle_brush_id];
                meshBuffer->Material = B3dMaterial->Material;
            }
            else
                B3dMaterial = 0;

            const s32 memoryNeeded = B3dStack.getLast().length / sizeof(s32);
            meshBuffer->Indices.reallocate(memoryNeeded + meshBuffer->Indices.size() + 1);

            while((B3dStack.getLast().startposition + B3dStack.getLast().length) > B3DFile->getPos()) // this chunk repeats
            {
                s32 vertex_id[3];

                B3DFile->read(vertex_id, 3*sizeof(s32));
#ifdef __BIG_ENDIAN__
                vertex_id[0] = os::Byteswap::byteswap(vertex_id[0]);
                vertex_id[1] = os::Byteswap::byteswap(vertex_id[1]);
                vertex_id[2] = os::Byteswap::byteswap(vertex_id[2]);
#endif

                //Make Ids global:
                vertex_id[0] += vertices_Start;
                vertex_id[1] += vertices_Start;
                vertex_id[2] += vertices_Start;

                for(s32 i=0; i<3; ++i)
                {
                    if ((u32)vertex_id[i] >= AnimatedVertices_VertexID.size())
                    {
                        os::Printer::log("Illegal vertex index found", B3DFile->getFileName(), ELL_ERROR);
                        return false;
                    }

                    if (AnimatedVertices_VertexID[ vertex_id[i] ] != -1)
                    {
                        if ( AnimatedVertices_BufferID[ vertex_id[i] ] != (s32)meshBufferID ) //If this vertex is linked in a different meshbuffer
                        {
                            AnimatedVertices_VertexID[ vertex_id[i] ] = -1;
                            AnimatedVertices_BufferID[ vertex_id[i] ] = -1;
                            showVertexWarning=true;
                        }
                    }
                    if (AnimatedVertices_VertexID[ vertex_id[i] ] == -1) //If this vertex is not in the meshbuffer
                    {
                        //Check for lightmapping:
                        if (BaseVertices[ vertex_id[i] ].TCoords2 != core::vector2df(0.f,0.f))
                            meshBuffer->convertTo2TCoords(); //Will only affect the meshbuffer the first time this is called

                        //Add the vertex to the meshbuffer:
                        if (meshBuffer->VertexType == video::EVT_STANDARD)
                            meshBuffer->Vertices_Standard.push_back( BaseVertices[ vertex_id[i] ] );
                        else
                            meshBuffer->Vertices_2TCoords.push_back(BaseVertices[ vertex_id[i] ] );

                        //create vertex id to meshbuffer index link:
                        AnimatedVertices_VertexID[ vertex_id[i] ] = meshBuffer->getVertexCount()-1;
                        AnimatedVertices_BufferID[ vertex_id[i] ] = meshBufferID;

                        if (B3dMaterial)
                        {
                            // Apply Material/Color/etc...
                            video::S3DVertex *Vertex=meshBuffer->getVertex(meshBuffer->getVertexCount()-1);

                            if (!HasVertexColors)
                                Vertex->Color=B3dMaterial->Material.DiffuseColor;
                            else if (Vertex->Color.getAlpha() == 255)
                                Vertex->Color.setAlpha( (s32)(B3dMaterial->alpha * 255.0f) );

                            // Use texture's scale
                            if (B3dMaterial->Textures[0])
                            {
                                Vertex->TCoords.X *= B3dMaterial->Textures[0]->Xscale;
                                Vertex->TCoords.Y *= B3dMaterial->Textures[0]->Yscale;
                            }
                            /*
                            if (B3dMaterial->Textures[1])
                            {
                            Vertex->TCoords2.X *=B3dMaterial->Textures[1]->Xscale;
                            Vertex->TCoords2.Y *=B3dMaterial->Textures[1]->Yscale;
                            }
                            */
                        }
                    }
                }

                meshBuffer->Indices.push_back( AnimatedVertices_VertexID[ vertex_id[0] ] );
                meshBuffer->Indices.push_back( AnimatedVertices_VertexID[ vertex_id[1] ] );
                meshBuffer->Indices.push_back( AnimatedVertices_VertexID[ vertex_id[2] ] );
            }

            B3dStack.erase(B3dStack.size()-1);

            if (showVertexWarning)
                os::Printer::log("B3dMeshLoader: Warning, different meshbuffers linking to the same vertex, this will cause problems with animated meshes");

            return true;
        }


        bool CB3DMeshFileLoader::readChunkBONE(CSkinnedMesh::SJoint *inJoint)
        {
#ifdef _B3D_READER_DEBUG
            os::Printer::log("read ChunkBONE");
#endif

            if (B3dStack.getLast().length > 8)
            {
                while((B3dStack.getLast().startposition + B3dStack.getLast().length) > B3DFile->getPos()) // this chunk repeats
                {
                    u32 globalVertexID;
                    f32 strength;
                    B3DFile->read(&globalVertexID, sizeof(globalVertexID));
                    B3DFile->read(&strength, sizeof(strength));
#ifdef __BIG_ENDIAN__
                    globalVertexID = os::Byteswap::byteswap(globalVertexID);
                    strength = os::Byteswap::byteswap(strength);
#endif

                    if (AnimatedVertices_VertexID[globalVertexID]==-1)
                    {
                        os::Printer::log("B3dMeshLoader: Weight has bad vertex id (no link to meshbuffer index found)");
                    }
                    else if (strength >0)
                    {
                        CSkinnedMesh::SWeight *weight=AnimatedMesh->addWeight(inJoint);
                        weight->strength=strength;
                        //Find the meshbuffer and Vertex index from the Global Vertex ID:
                        weight->vertex_id = AnimatedVertices_VertexID[globalVertexID];
                        weight->buffer_id = AnimatedVertices_BufferID[globalVertexID];
                    }
                }
            }

            B3dStack.erase(B3dStack.size()-1);
            return true;
        }


        bool CB3DMeshFileLoader::readChunkKEYS(CSkinnedMesh::SJoint *inJoint)
        {
#ifdef _B3D_READER_DEBUG
            //	os::Printer::log("read ChunkKEYS");
#endif

            s32 flags;
            B3DFile->read(&flags, sizeof(flags));
#ifdef __BIG_ENDIAN__
            flags = os::Byteswap::byteswap(flags);
#endif

            CSkinnedMesh::SPositionKey *oldPosKey=0;
            core::vector3df oldPos[2];
            CSkinnedMesh::SScaleKey *oldScaleKey=0;
            core::vector3df oldScale[2];
            CSkinnedMesh::SRotationKey *oldRotKey=0;
            core::quaternion oldRot[2];
            bool isFirst[3]={true,true,true};
            while((B3dStack.getLast().startposition + B3dStack.getLast().length) > B3DFile->getPos()) //this chunk repeats
            {
                s32 frame;

                B3DFile->read(&frame, sizeof(frame));
#ifdef __BIG_ENDIAN__
                frame = os::Byteswap::byteswap(frame);
#endif

                // Add key frames, frames in Irrlicht are zero-based
                f32 data[4];
                if (flags & 1)
                {
                    readFloats(data, 3);
                    if ((oldPosKey!=0) && (oldPos[0]==oldPos[1]))
                    {
                        const core::vector3df pos(data[0], data[1], data[2]);
                        if (oldPos[1]==pos)
                            oldPosKey->frame = (f32)frame-1;
                        else
                        {
                            oldPos[0]=oldPos[1];
                            oldPosKey=AnimatedMesh->addPositionKey(inJoint);
                            oldPosKey->frame = (f32)frame-1;
                            oldPos[1].set(oldPosKey->position.set(pos));
                        }
                    }
                    else if (oldPosKey==0 && isFirst[0])
                    {
                        oldPosKey=AnimatedMesh->addPositionKey(inJoint);
                        oldPosKey->frame = (f32)frame-1;
                        oldPos[0].set(oldPosKey->position.set(data[0], data[1], data[2]));
                        oldPosKey=0;
                        isFirst[0]=false;
                    }
                    else
                    {
                        if (oldPosKey!=0)
                            oldPos[0]=oldPos[1];
                        oldPosKey=AnimatedMesh->addPositionKey(inJoint);
                        oldPosKey->frame = (f32)frame-1;
                        oldPos[1].set(oldPosKey->position.set(data[0], data[1], data[2]));
                    }
                }
                if (flags & 2)
                {
                    readFloats(data, 3);
                    if ((oldScaleKey!=0) && (oldScale[0]==oldScale[1]))
                    {
                        const core::vector3df scale(data[0], data[1], data[2]);
                        if (oldScale[1]==scale)
                            oldScaleKey->frame = (f32)frame-1;
                        else
                        {
                            oldScale[0]=oldScale[1];
                            oldScaleKey=AnimatedMesh->addScaleKey(inJoint);
                            oldScaleKey->frame = (f32)frame-1;
                            oldScale[1].set(oldScaleKey->scale.set(scale));
                        }
                    }
                    else if (oldScaleKey==0 && isFirst[1])
                    {
                        oldScaleKey=AnimatedMesh->addScaleKey(inJoint);
                        oldScaleKey->frame = (f32)frame-1;
                        oldScale[0].set(oldScaleKey->scale.set(data[0], data[1], data[2]));
                        oldScaleKey=0;
                        isFirst[1]=false;
                    }
                    else
                    {
                        if (oldScaleKey!=0)
                            oldScale[0]=oldScale[1];
                        oldScaleKey=AnimatedMesh->addScaleKey(inJoint);
                        oldScaleKey->frame = (f32)frame-1;
                        oldScale[1].set(oldScaleKey->scale.set(data[0], data[1], data[2]));
                    }
                }
                if (flags & 4)
                {
                    readFloats(data, 4);
                    if ((oldRotKey!=0) && (oldRot[0]==oldRot[1]))
                    {
                        // meant to be in this order since b3d stores W first
                        const core::quaternion rot(data[1], data[2], data[3], data[0]);
                        if (oldRot[1]==rot)
                            oldRotKey->frame = (f32)frame-1;
                        else
                        {
                            oldRot[0]=oldRot[1];
                            oldRotKey=AnimatedMesh->addRotationKey(inJoint);
                            oldRotKey->frame = (f32)frame-1;
                            oldRot[1].set(oldRotKey->rotation.set(data[1], data[2], data[3], data[0]));
                        }
                    }
                    else if (oldRotKey==0 && isFirst[2])
                    {
                        oldRotKey=AnimatedMesh->addRotationKey(inJoint);
                        oldRotKey->frame = (f32)frame-1;
                        // meant to be in this order since b3d stores W first
                        oldRot[0].set(oldRotKey->rotation.set(data[1], data[2], data[3], data[0]));
                        oldRotKey=0;
                        isFirst[2]=false;
                    }
                    else
                    {
                        if (oldRotKey!=0)
                            oldRot[0]=oldRot[1];
                        oldRotKey=AnimatedMesh->addRotationKey(inJoint);
                        oldRotKey->frame = (f32)frame-1;
                        // meant to be in this order since b3d stores W first
                        oldRot[1].set(oldRotKey->rotation.set(data[1], data[2], data[3], data[0]));
                    }
                }
            }

            B3dStack.erase(B3dStack.size()-1);
            return true;
        }


        bool CB3DMeshFileLoader::readChunkANIM()
        {
#ifdef _B3D_READER_DEBUG
            os::Printer::log("read ChunkANIM");
#endif

            s32 animFlags; //not stored\used
            s32 animFrames;//not stored\used
            f32 animFPS; //not stored\used

            B3DFile->read(&animFlags, sizeof(s32));
            B3DFile->read(&animFrames, sizeof(s32));
            readFloats(&animFPS, 1);

#ifdef __BIG_ENDIAN__
            animFlags = os::Byteswap::byteswap(animFlags);
            animFrames = os::Byteswap::byteswap(animFrames);
#endif

            B3dStack.erase(B3dStack.size()-1);
            return true;
        }


        bool CB3DMeshFileLoader::readChunkTEXS()
        {
#ifdef _B3D_READER_DEBUG
            os::Printer::log("read ChunkTEXS");
#endif

            while((B3dStack.getLast().startposition + B3dStack.getLast().length) > B3DFile->getPos()) //this chunk repeats
            {
                Textures.push_back(SB3dTexture());
                SB3dTexture& B3dTexture = Textures.getLast();

                readString(B3dTexture.TextureName);
                B3dTexture.TextureName.replace('\\','/');
#ifdef _B3D_READER_DEBUG
                os::Printer::log("read Texture", B3dTexture.TextureName.c_str());
#endif

                B3DFile->read(&B3dTexture.Flags, sizeof(s32));
                B3DFile->read(&B3dTexture.Blend, sizeof(s32));
#ifdef __BIG_ENDIAN__
                B3dTexture.Flags = os::Byteswap::byteswap(B3dTexture.Flags);
                B3dTexture.Blend = os::Byteswap::byteswap(B3dTexture.Blend);
#endif
#ifdef _B3D_READER_DEBUG
                os::Printer::log("Flags", core::stringc(B3dTexture.Flags).c_str());
                os::Printer::log("Blend", core::stringc(B3dTexture.Blend).c_str());
#endif
                readFloats(&B3dTexture.Xpos, 1);
                readFloats(&B3dTexture.Ypos, 1);
                readFloats(&B3dTexture.Xscale, 1);
                readFloats(&B3dTexture.Yscale, 1);
                readFloats(&B3dTexture.Angle, 1);
            }

            B3dStack.erase(B3dStack.size()-1);

            return true;
        }


        bool CB3DMeshFileLoader::readChunkBRUS()
        {
#ifdef _B3D_READER_DEBUG
            os::Printer::log("read ChunkBRUS");
#endif

            u32 n_texs;
            B3DFile->read(&n_texs, sizeof(u32));
#ifdef __BIG_ENDIAN__
            n_texs = os::Byteswap::byteswap(n_texs);
#endif

            // number of texture ids read for Irrlicht
            const u32 num_textures = core::min_(n_texs, video::MATERIAL_MAX_TEXTURES);
            // number of bytes to skip (for ignored texture ids)
            const u32 n_texs_offset = (num_textures<n_texs)?(n_texs-num_textures):0;

            while((B3dStack.getLast().startposition + B3dStack.getLast().length) > B3DFile->getPos()) //this chunk repeats
            {
                // This is what blitz basic calls a brush, like a Irrlicht Material

                core::stringc name;
                readString(name);
#ifdef _B3D_READER_DEBUG
                os::Printer::log("read Material", name);
#endif
                Materials.push_back(SB3dMaterial());
                SB3dMaterial& B3dMaterial=Materials.getLast();

                readFloats(&B3dMaterial.red, 1);
                readFloats(&B3dMaterial.green, 1);
                readFloats(&B3dMaterial.blue, 1);
                readFloats(&B3dMaterial.alpha, 1);
                readFloats(&B3dMaterial.shininess, 1);

                B3DFile->read(&B3dMaterial.blend, sizeof(B3dMaterial.blend));
                B3DFile->read(&B3dMaterial.fx, sizeof(B3dMaterial.fx));
#ifdef __BIG_ENDIAN__
                B3dMaterial.blend = os::Byteswap::byteswap(B3dMaterial.blend);
                B3dMaterial.fx = os::Byteswap::byteswap(B3dMaterial.fx);
#endif
#ifdef _B3D_READER_DEBUG
                os::Printer::log("Blend", core::stringc(B3dMaterial.blend).c_str());
                os::Printer::log("FX", core::stringc(B3dMaterial.fx).c_str());
#endif

                u32 i;
                for (i=0; i<num_textures; ++i)
                {
                    s32 texture_id=-1;
                    B3DFile->read(&texture_id, sizeof(s32));
#ifdef __BIG_ENDIAN__
                    texture_id = os::Byteswap::byteswap(texture_id);
#endif
                    //--- Get pointers to the texture, based on the IDs ---
                    if ((u32)texture_id < Textures.size())
                    {
                        B3dMaterial.Textures[i]=&Textures[texture_id];
#ifdef _B3D_READER_DEBUG
                        os::Printer::log("Layer", core::stringc(i).c_str());
                        os::Printer::log("using texture", Textures[texture_id].TextureName.c_str());
#endif
                    }
                    else
                        B3dMaterial.Textures[i]=0;
                }
                // skip other texture ids
                for (i=0; i<n_texs_offset; ++i)
                {
                    s32 texture_id=-1;
                    B3DFile->read(&texture_id, sizeof(s32));
#ifdef __BIG_ENDIAN__
                    texture_id = os::Byteswap::byteswap(texture_id);
#endif
                    if (ShowWarning && (texture_id != -1) && (n_texs>video::MATERIAL_MAX_TEXTURES))
                    {
                        os::Printer::log("Too many textures used in one material", B3DFile->getFileName(), ELL_WARNING);
                        ShowWarning = false;
                    }
                }

                //Fixes problems when the lightmap is on the first texture:
                if (B3dMaterial.Textures[0] != 0)
                {
                    if (B3dMaterial.Textures[0]->Flags & 65536) // 65536 = secondary UV
                    {
                        SB3dTexture *TmpTexture;
                        TmpTexture = B3dMaterial.Textures[1];
                        B3dMaterial.Textures[1] = B3dMaterial.Textures[0];
                        B3dMaterial.Textures[0] = TmpTexture;
                    }
                }

                //If a preceeding texture slot is empty move the others down:
                for (i=num_textures; i>0; --i)
                {
                    for (u32 j=i-1; j<num_textures-1; ++j)
                    {
                        if (B3dMaterial.Textures[j+1] != 0 && B3dMaterial.Textures[j] == 0)
                        {
                            B3dMaterial.Textures[j] = B3dMaterial.Textures[j+1];
                            B3dMaterial.Textures[j+1] = 0;
                        }
                    }
                }

                //------ Convert blitz flags/blend to irrlicht -------

                //Two textures:
                if (B3dMaterial.Textures[1])
                {
                    if (B3dMaterial.alpha==1.f)
                    {
                        if (B3dMaterial.Textures[1]->Blend == 5) //(Multiply 2)
                            B3dMaterial.Material.MaterialType = video::EMT_LIGHTMAP_M2;
                        else
                            B3dMaterial.Material.MaterialType = video::EMT_LIGHTMAP;
                        B3dMaterial.Material.Lighting = false;
                    }
                    else
                    {
                        B3dMaterial.Material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
                        B3dMaterial.Material.ZWriteEnable = false;
                    }
                }
                else if (B3dMaterial.Textures[0]) //One texture:
                {
                    // Flags & 0x1 is usual SOLID, 0x8 is mipmap (handled before)
                    if (B3dMaterial.Textures[0]->Flags & 0x2) //(Alpha mapped)
                    {
                        B3dMaterial.Material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
                        B3dMaterial.Material.ZWriteEnable = false;
                    }
                    else if (B3dMaterial.Textures[0]->Flags & 0x4) //(Masked)
                        B3dMaterial.Material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; // TODO: create color key texture
                    else if (B3dMaterial.Textures[0]->Flags & 0x40)
                        B3dMaterial.Material.MaterialType = video::EMT_SPHERE_MAP;
                    else if (B3dMaterial.Textures[0]->Flags & 0x80)
                        B3dMaterial.Material.MaterialType = video::EMT_SPHERE_MAP; // TODO: Should be cube map
                    else if (B3dMaterial.alpha == 1.f)
                        B3dMaterial.Material.MaterialType = video::EMT_SOLID;
                    else
                    {
                        B3dMaterial.Material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
                        B3dMaterial.Material.ZWriteEnable = false;
                    }
                }
                else //No texture:
                {
                    if (B3dMaterial.alpha == 1.f)
                        B3dMaterial.Material.MaterialType = video::EMT_SOLID;
                    else
                    {
                        B3dMaterial.Material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
                        B3dMaterial.Material.ZWriteEnable = false;
                    }
                }

                B3dMaterial.Material.DiffuseColor = video::SColorf(B3dMaterial.red, B3dMaterial.green, B3dMaterial.blue, B3dMaterial.alpha).toSColor();
                B3dMaterial.Material.ColorMaterial=video::ECM_NONE;

                //------ Material fx ------

                if (B3dMaterial.fx & 1) //full-bright
                {
                    B3dMaterial.Material.AmbientColor = video::SColor(255, 255, 255, 255);
                    B3dMaterial.Material.Lighting = false;
                }
                else
                    B3dMaterial.Material.AmbientColor = B3dMaterial.Material.DiffuseColor;

                if (B3dMaterial.fx & 2) //use vertex colors instead of brush color
                    B3dMaterial.Material.ColorMaterial=video::ECM_DIFFUSE_AND_AMBIENT;

                if (B3dMaterial.fx & 4) //flatshaded
                    B3dMaterial.Material.GouraudShading = false;

                if (B3dMaterial.fx & 16) //disable backface culling
                    B3dMaterial.Material.BackfaceCulling = false;

                if (B3dMaterial.fx & 32) //force vertex alpha-blending
                {
                    B3dMaterial.Material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
                    B3dMaterial.Material.ZWriteEnable = false;
                }

                B3dMaterial.Material.Shininess = B3dMaterial.shininess;
            }

            B3dStack.erase(B3dStack.size()-1);

            return true;
        }


        void CB3DMeshFileLoader::loadTextures(SB3dMaterial& material) const
        {
            const bool previous32BitTextureFlag = SceneManager->getVideoDriver()->getTextureCreationFlag(video::ETCF_ALWAYS_32_BIT);
            SceneManager->getVideoDriver()->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);

            // read texture from disk
            // note that mipmaps might be disabled by Flags & 0x8
            const bool doMipMaps = SceneManager->getVideoDriver()->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS);

            for (u32 i=0; i<video::MATERIAL_MAX_TEXTURES; ++i)
            {
                SB3dTexture* B3dTexture = material.Textures[i];
                if (B3dTexture && B3dTexture->TextureName.size() && !material.Material.getTexture(i))
                {
                    if (!SceneManager->getParameters()->getAttributeAsBool(B3D_LOADER_IGNORE_MIPMAP_FLAG))
                        SceneManager->getVideoDriver()->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, (B3dTexture->Flags & 0x8) ? true:false);
                    {
                        video::ITexture* tex = 0;
                        io::IFileSystem* fs = SceneManager->getFileSystem();
                        io::path texnameWithUserPath( SceneManager->getParameters()->getAttributeAsString(B3D_TEXTURE_PATH) );
                        if ( texnameWithUserPath.size() )
                        {
                            texnameWithUserPath += '/';
                            texnameWithUserPath += B3dTexture->TextureName;
                        }
                        if (fs->existFile(texnameWithUserPath))
                            tex = SceneManager->getVideoDriver()->getTexture(texnameWithUserPath);
                        else if (fs->existFile(B3dTexture->TextureName))
                            tex = SceneManager->getVideoDriver()->getTexture(B3dTexture->TextureName);
                        else if (fs->existFile(fs->getFileDir(B3DFile->getFileName()) +"/"+ fs->getFileBasename(B3dTexture->TextureName)))
                            tex = SceneManager->getVideoDriver()->getTexture(fs->getFileDir(B3DFile->getFileName()) +"/"+ fs->getFileBasename(B3dTexture->TextureName));
                        else
                            tex = SceneManager->getVideoDriver()->getTexture(fs->getFileBasename(B3dTexture->TextureName));

                        material.Material.setTexture(i, tex);
                    }
                    if (material.Textures[i]->Flags & 0x10) // Clamp U
                        material.Material.TextureLayer[i].TextureWrapU=video::ETC_CLAMP;
                    if (material.Textures[i]->Flags & 0x20) // Clamp V
                        material.Material.TextureLayer[i].TextureWrapV=video::ETC_CLAMP;
                }
            }

            SceneManager->getVideoDriver()->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, doMipMaps);
            SceneManager->getVideoDriver()->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, previous32BitTextureFlag);
        }


        void CB3DMeshFileLoader::readString(core::stringc& newstring)
        {
            newstring="";
            while (B3DFile->getPos() <= B3DFile->getSize())
            {
                c8 character;
                B3DFile->read(&character, sizeof(character));
                if (character==0)
                    return;
                newstring.append(character);
            }
        }


        void CB3DMeshFileLoader::readFloats(f32* vec, u32 count)
        {
            B3DFile->read(vec, count*sizeof(f32));
#ifdef __BIG_ENDIAN__
            for (u32 n=0; n<count; ++n)
                vec[n] = os::Byteswap::byteswap(vec[n]);
#endif
        }

    } // end namespace scene
} // end namespace irr


#endif // _IRR_COMPILE_WITH_B3D_LOADER_

